/* *
 * --ライセンスについて--
 *
 * 「本ファイルの内容は Mozilla Public License Version 1.1 (「本ライセンス」)
 * の適用を受けます。
 * 本ライセンスに従わない限り本ファイルを使用することはできません。
 * 本ライセンスのコピーは http://www.mozilla.org/MPL/ から入手できます。
 *
 * 本ライセンスに基づき配布されるソフトウェアは、「現状のまま」で配布されるものであり、
 * 明示的か黙示的かを問わず、いかなる種類の保証も行われません。
 * 本ライセンス上の権利および制限を定める具体的な文言は、本ライセンスを参照してください。
 *
 * オリジナルコードおよび初期開発者は、N_H (h.10x64@gmail.com) です。
 *
 * N_H によって作成された部分の著作権表示は次のとおりです。
 *
 * Copyright (C) 2011 - 2012
 *
 * このファイルの内容は、上記に代えて、
 * GNU General License version2 以降 (以下 GPL とする)、
 * GNU Lesser General Public License Version 2.1 以降 (以下 LGPL とする)、
 * の条件に従って使用することも可能です。
 * この場合、このファイルの使用には上記の条項ではなく GPL または LGPL の条項が適用されます。
 * このファイルの他者による使用を GPL または LGPL の条件によってのみ許可し、
 * MPL による使用を許可したくない対象者は、上記の条項を削除することでその意思を示し、
 * 上記条項を GPL または LGPL で義務付けられている告知およびその他の条項に置き換えてください。
 * 対象者が上記の条項を削除しない場合、
 * 受領者は MPL または GPL または LGPL ライセンスのいずれによってもこのファイルを
 * 使用することができます。」
 *
 * -- License --
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License。You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND、either express or implied。See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Initial Developer of the Original Code is
 *   N_H (h.10x64@gmail.com).
 *
 * Portions created by the Initial Developer are Copyright (C) 2011 - 2012
 * the Initial Developer。All Rights Reserved.
 *
 * Alternatively、the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL")、or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above。If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL、and not to allow others to
 * use your version of this file under the terms of the MPL、indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL。If you do not delete
 * the provisions above、a recipient may use your version of this file under
 * the terms of any one of the MPL、the GPL or the LGPL.
 *
 * */
package com.magiciansforest.audio.vst.binaural;

/**
 *
 * @author N_H <h.10x64@gmail.com>
 */
public class Matrix4 {

    public static final int XYZ = 0;
    public static final int XZY = 1;
    public static final int YXZ = 2;
    public static final int YZX = 3;
    public static final int ZXY = 4;
    public static final int ZYX = 5;
    public double[][] m;

    public Matrix4() {
        m = new double[][]{
            {1, 0, 0, 0},
            {0, 1, 0, 0},
            {0, 0, 1, 0},
            {0, 0, 0, 1},};
    }

    public Matrix4(Matrix4 m) {
        this(m.m[0][0], m.m[0][1], m.m[0][2], m.m[0][3],
                m.m[1][0], m.m[1][1], m.m[1][2], m.m[1][3],
                m.m[2][0], m.m[2][1], m.m[2][2], m.m[2][3],
                m.m[3][0], m.m[3][1], m.m[3][2], m.m[3][3]);
    }

    public Matrix4(double m00, double m01, double m02, double m03,
            double m10, double m11, double m12, double m13,
            double m20, double m21, double m22, double m23,
            double m30, double m31, double m32, double m33) {
        m = new double[4][4];
        m[0][0] = m00;
        m[0][1] = m01;
        m[0][2] = m02;
        m[0][3] = m03;
        m[1][0] = m10;
        m[1][1] = m11;
        m[1][2] = m12;
        m[1][3] = m13;
        m[2][0] = m20;
        m[2][1] = m21;
        m[2][2] = m22;
        m[2][3] = m23;
        m[3][0] = m30;
        m[3][1] = m31;
        m[3][2] = m32;
        m[3][3] = m33;
    }

    public static Matrix4 getReverse(Matrix4 m) {
        Matrix4 ret = new Matrix4(m);
        ret.reverse();
        return ret;
    }
    
    public static Matrix4 getMul(Matrix4 left, Matrix4 right) {
        Matrix4 ret = new Matrix4(left);
        ret.mul(right);
        return ret;
    }

    public static Matrix4 getRotX(double rx) {
        return new Matrix4(
                1, 0, 0, 0,
                0, Math.cos(rx), Math.sin(rx), 0,
                0, -Math.sin(rx), Math.cos(rx), 0,
                0, 0, 0, 1);
    }

    public static Matrix4 getRotY(double ry) {
        return new Matrix4(
                Math.cos(ry), 0, -Math.sin(ry), 0,
                0, 1, 0, 0,
                Math.sin(ry), 0, Math.cos(ry), 0,
                0, 0, 0, 1);
    }

    public static Matrix4 getRotZ(double rz) {
        return new Matrix4(
                Math.cos(rz), Math.sin(rz), 0, 0,
                -Math.sin(rz), Math.cos(rz), 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);
    }

    public static Matrix4 getRot(double rx, double ry, double rz, int mode) {
        Matrix4 ret;
        switch (mode) {
            case XYZ:
                ret = getRotX(rx);
                ret.mul(getRotY(ry));
                ret.mul(getRotZ(rz));
                break;
            case XZY:
                ret = getRotX(rx);
                ret.mul(getRotZ(rz));
                ret.mul(getRotY(ry));
                break;
            case YXZ:
                ret = getRotY(ry);
                ret.mul(getRotX(rx));
                ret.mul(getRotZ(rz));
                break;
            case YZX:
                ret = getRotY(ry);
                ret.mul(getRotZ(rz));
                ret.mul(getRotX(rx));
                break;
            case ZYX:
                ret = getRotZ(rz);
                ret.mul(getRotY(ry));
                ret.mul(getRotX(rx));
                break;
            default: //ZXY
                ret = getRotZ(rz);
                ret.mul(getRotX(rx));
                ret.mul(getRotY(ry));
                break;
        }
        return ret;
    }

    public static Matrix4 getTranslate(Vector3 v) {
        return getTranslate(v.x, v.y, v.z);
    }

    public static Matrix4 getTranslate(double dx, double dy, double dz) {
        return new Matrix4(
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                dx, dy, dz, 1);
    }

    public static Matrix4 getMatrix(double rx, double ry, double rz, double dx, double dy, double dz, int mode) {
        Matrix4 ret = getRot(rx, ry, rz, mode);
        ret.mul(getTranslate(dx, dy, dz));
        return ret;
    }

    public void mul(Matrix4 mat) {
        double[][] buf = new double[4][4];
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                for (int k = 0; k < 4; k++) {
                    buf[i][j] += m[i][k] * mat.m[k][j];
                }
            }
        }
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                m[i][j] = buf[i][j];
            }
        }
    }

    public void reverse() {
        double t, u, d;
        d = 1.0;
        for (int i = 0; i < 4; i++) {
            t = m[i][i];
            d *= t;
            for (int j = 0; j < 4; j++) {
                m[i][j] /= t;
            }
            m[i][i] = 1.0 / t;
            for (int k = 0; k < 4; k++) {
                if (k != i) {
                    u = m[k][i];
                    for (int j = 0; j < 4; j++) {
                        if (j != i) {
                            m[k][j] -= m[i][j] * u;
                        } else {
                            m[k][j] = -u / t;
                        }
                    }
                }
            }
        }
    }

    public void transform(Vector3 v) {
        Matrix4 ret = getTranslate(v);

        ret.mul(this);

        v.x = ret.m[3][0];
        v.y = ret.m[3][1];
        v.z = ret.m[3][2];
    }

    public Vector3 getLocalX() {
        return new Vector3(m[0][0], m[0][1], m[0][2]);
    }

    public Vector3 getLocalY() {
        return new Vector3(m[1][0], m[1][1], m[1][2]);
    }

    public Vector3 getLocalZ() {
        return new Vector3(m[2][0], m[2][1], m[2][2]);
    }

    public Vector3 getPosition() {
        return new Vector3(m[3][0], m[3][1], m[3][2]);
    }

    public Spherical getDirection() {
        Vector3 lz = getLocalZ();
        return lz.toSpherical();
    }

    public Vector3 toVector3() {
        return getPosition();
    }

    @Override
    public String toString() {
        return "{\n"
                + "  " + m[0][0] + " " + m[0][1] + " " + m[0][2] + " " + m[0][3] + "\n"
                + "  " + m[1][0] + " " + m[1][1] + " " + m[1][2] + " " + m[1][3] + "\n"
                + "  " + m[2][0] + " " + m[2][1] + " " + m[2][2] + " " + m[2][3] + "\n"
                + "  " + m[3][0] + " " + m[3][1] + " " + m[3][2] + " " + m[3][3] + "\n"
                + "}";
    }
}
